#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdarg.h>

#include <netinet/in.h>

#include "messages.h"
#include "client_serialize.h"

/**
 * put_u8(buf, data)
 * Puts an 8 byte structure into the desired network buffer.
 */
void put_u8(char *buf, uint8_t data)
{
	memcpy(buf, &data, 1);
}

/**
 * put_u16(buf, data)
 * Puts a 16 byte structure into the desired network buffer.
 */
void put_u16(char *buf, uint16_t data)
{
	uint16_t data_nbo = htons(data);
	memcpy(buf, &data_nbo, 2);
}

/**
 * get_u8(buf)
 * Gets an 8 byte structure from the desired network buffer.
 */
uint8_t get_u8(char *buf)
{
	uint8_t data;
	memcpy(&data, buf, 1);
	return data;
}

/**
 * get_u16(buf)
 * Gets a 16 byte structure from the desired network buffer.
 */
uint16_t get_u16(char *buf)
{
	uint16_t data;
	memcpy(&data, buf, 2);
	return ntohs(data);
}

/**
 * pack(buf, format, ...)
 * Builds a network message by concatenating data in var-args according to
 * the format string and the following rules:
 *  - 'c' = uint8_t;
 *  - 'h' = uint16_t;
 *  - 's' = null-terminated string;
 */
uint16_t pack(char *buf, const char *format, ...)
{
	char *p = buf;

    va_list ap;
    va_start(ap, format);

    for(; *format != '\0'; format++) {
		uint8_t c;
		uint16_t h;
		char *s;
		int len;
		
        switch(*format) {
        case 'c': // 8-bit
            c = (uint8_t)va_arg(ap, int); // promoted
			put_u8(p, c);
			p += 1;
            break;

        case 'h': // 16-bit
            h = (int16_t)va_arg(ap, int); // promoted
			put_u16(p, h);
			p += 2;
            break;

        case 's': // string
            s = va_arg(ap, char*);
            len = strlen(s) + 1;
			memcpy(p, s, len);
			p += len;
            break;
        }
    }

    va_end(ap);

    return p - buf;
}

/**
 * unpack(buf, format, ...)
 * Deconstructs a network message into its working pargs by deconcatenating 
 * data in var-args according to the format string and the following rules:
 *  - 'c' = uint8_t;
 *  - 'h' = uint16_t;
 *  - 's' = null-terminated string;
 */
uint16_t unpack(char *buf, const char *format, ...)
{
	char *p = buf;

    va_list ap;
    va_start(ap, format);

    for(; *format != '\0'; format++) {
		uint8_t *c;
		uint16_t *h;
		char *s;
		int len;
		
        switch(*format) {
        case 'c': // 8-bit
            c = (uint8_t*)va_arg(ap, uint8_t*);
			*c = get_u8(p);
			p += 1;
            break;

        case 'h': // 16-bit
            h = (uint16_t*)va_arg(ap, uint16_t*); 
			*h = get_u16(p);
			p += 2;
            break;

        case 's': // string
            s = va_arg(ap, char*);
            len = strlen(p) + 1;
			memcpy(s, p, len);
			p += len;
            break;
        }
    }

    va_end(ap);

	return p - buf;
}

/**
 * encapsulate(buf, length, message)
 * Encapsulates a message for network transmission by concatenating
 * it with its 2-byte length.
 */
int encapsulate(char *buf, uint16_t length, char *message)
{
	char *p = buf;
	uint16_t length_nbo = htons(length);
	memcpy(p, &length_nbo, 2);
	p += 2;
	memcpy(p, message, length);
	p += length;
	return p - buf;
}

/**
 * serializeLC(buf, msg)
 * Serializes a MessageLC structure.
 */
uint16_t serializeLC(char *buf, MessageLC *msg)
{
	switch (msg->type)
	{
	case LC_ROOM_LIST:
		return pack(buf, "c", msg->type);
		
	case LC_CREATE_ROOM:
		{
			MessageLCCreateRoom *message = (MessageLCCreateRoom*)msg;
			return pack(buf, "cs", message->type, message->room_name);
		}
	
	case LC_DELETE_ROOM:
		{
			MessageLCDeleteRoom *message = (MessageLCDeleteRoom*)msg;
			return pack(buf, "ch", message->type, message->room_port);
		}
		
	default:
		return -1;
	}
}

/**
 * serializeLS(buf, msg)
 * Serializes a MessageLS structure.
 */
uint16_t serializeLS(char *buf, MessageLS *msg)
{
	switch (msg->type)
	{
	case LS_ASSOCIATE:
		{
			MessageLSAssociate *message = (MessageLSAssociate*)msg;
			return pack(buf, "cs", message->type, message->user_name);
		}
	
	case LS_DEASSOCIATE:
		return pack(buf, "c", msg->type);
	
	case LS_MESSAGE:
		{
			MessageLSMessage *message = (MessageLSMessage*)msg;
			return pack(buf, "cs", message->type, message->message);
		}
		
	default:
		return -1;
	}
}

/**
 * deserializeCL(buf)
 * Deserializes a MessageCL structure.
 */
MessageCL *deserializeCL(char *buf)
{
	uint8_t p_type;
	buf += unpack(buf, "c", &p_type);
	MessageTypeCL type = (MessageTypeCL)p_type;
	switch(type)
	{
	case CL_ROOM_LIST:
		{
			MessageCLRoomList *message = new MessageCLRoomList;
			message->type = type;
			uint8_t room_count;
			buf += unpack(buf, "c", &room_count);
			message->room_count = room_count;
			for (int i = 0; i < room_count; ++i)
			{
				uint16_t room_port;
				buf += unpack(buf, "sh", message->rooms[i].room_name, &room_port);
				message->rooms[i].room_port = room_port;
			}
			return (MessageCL*)message;
		}
		
	case CL_ROOM_CREATED:
	case CL_ROOM_DELETED:
	case CL_ROOM_HAS_ASSOCIATES:
		{
			MessageCL *message = new MessageCL;
			message->type = type;
			return message;
		}
		
	default:
		return NULL;
	}
}

/**
 * deserializeSL(buf)
 * Deserializes a MessageSL structure.
 */
MessageSL *deserializeSL(char *buf)
{
	uint8_t p_type;
	buf += unpack(buf, "c", &p_type);
	MessageTypeSL type = (MessageTypeSL)p_type;
	switch(type)
	{
	case SL_ASSOCIATE_OK:
	case SL_DEASSOCIATE_OK:
		{
			MessageSL *message = new MessageSL;
			message->type = type;
			return message;
		}
		
	case SL_MESSAGE:
		{
			MessageSLMessage *message = new MessageSLMessage;
			message->type = type;
			unpack(buf, "s", message->message);
			return (MessageSL*)message;
		}
		
	default:
		return NULL;
	}	
}
